package runload

import (
	"fmt"
	"gitee.com/kingzyt/common/log"
	. "gitee.com/kingzyt/common/util"
	"math"
	"sort"
	"sync"
	"time"
)

type ActionStatInfo struct {
	ActionId         string
	TotalCnt         int32
	TotalFailSendCnt int32
	TotalFatalCnt    int32
	AvgResTMs        float32
	MinResTMs        int32
	MaxResTMs        int32
	MostResTMs       int32
	MostResPercent   float32
}

func (self *ActionStatInfo) String() string {
	return fmt.Sprintf("id:%s OK:%d FAIL:%d(FRL:%d),avgTMs:%f minTMs:%d maxTMs:%d mostTMs:%d~%d mostRate:%f\n",
		self.ActionId, self.TotalCnt, self.TotalFailSendCnt, self.TotalFatalCnt, self.AvgResTMs,
		self.MinResTMs, self.MaxResTMs, self.MostResTMs, self.MostResTMs+10, self.MostResPercent)
}

type ActionStatSort []*ActionStatInfo

func (self ActionStatSort) Len() int {
	return len(self)
}
func (self ActionStatSort) Less(i, j int) bool {
	return self[i].AvgResTMs > self[j].AvgResTMs // big to small
}
func (self ActionStatSort) Swap(i, j int) {
	self[i], self[j] = self[j], self[i]
}

func ShowActionTotalStats(module string, actionsStat map[string]*ActionStat) {
	stats := make([]*ActionStatInfo, len(actionsStat))
	stats = stats[:0]
	for _, stat := range actionsStat {
		stats = append(stats, stat.GetStat())
	}

	sort.Sort(ActionStatSort(stats))
	log.Info(module, log.I, "----------------------------------------")
	for i := 0; i < len(stats); i++ {
		log.Info(module, log.I, "%v", stats[i])
	}
	log.Info(module, log.I, "----------------------------------------")
}

type ActionStat struct {
	actionId       string
	lastReqT       int64
	minResTMs      int32
	maxResTMs      int32
	avgTotalResTMs int32
	avgTotalCnt    int32
	totalCnt       int32
	totalReqCnt    int32
	totalFatalCnt  int32
	resT           map[int32]int32 // <resT 10ms one step, count>
	statMutex      *sync.RWMutex
}

func NewActionStat(actionId string, statMutex *sync.RWMutex) *ActionStat {
	return &ActionStat{
		actionId:  actionId,
		resT:      map[int32]int32{},
		minResTMs: math.MaxInt32,
		maxResTMs: 0,
		statMutex: statMutex,
	}
}
func (self *ActionStat) AddReq() {
	self.statMutex.Lock()
	defer self.statMutex.Unlock()

	self.totalReqCnt++

	self.lastReqT = time.Now().UnixNano()
}

func (self *ActionStat) AddFatalErr() {
	self.statMutex.Lock()
	defer self.statMutex.Unlock()

	self.totalFatalCnt++
}

func (self *ActionStat) AddRes() {
	self.statMutex.Lock()
	defer self.statMutex.Unlock()

	now := time.Now().UnixNano()
	resTMs := int32(GetTimeSubMS(self.lastReqT, now))
	if resTMs > self.maxResTMs {
		self.maxResTMs = resTMs
	}
	if resTMs < self.minResTMs {
		self.minResTMs = resTMs
	}

	self.avgTotalResTMs += resTMs
	self.avgTotalCnt++
	self.totalCnt++

	tKey := int32(resTMs/10) * 10
	_, ok := self.resT[tKey]
	if !ok {
		self.resT[tKey] = 1
	} else {
		self.resT[tKey]++
	}
}

func (self *ActionStat) ResetAvg() {
	self.avgTotalResTMs = 0
	self.avgTotalCnt = 0
}

func (self *ActionStat) Merge(other *ActionStat) {
	if other.actionId != self.actionId {
		panic(fmt.Sprintf("other actionId [%s] is not the same as self actionId [%s]", other.actionId, self.actionId))
	}

	if other.minResTMs < self.minResTMs {
		self.minResTMs = other.minResTMs
	}
	if other.maxResTMs > self.maxResTMs {
		self.maxResTMs = other.maxResTMs
	}

	self.avgTotalResTMs += other.avgTotalResTMs
	self.avgTotalCnt += other.avgTotalCnt
	self.totalCnt += other.totalCnt
	self.totalReqCnt += other.totalReqCnt
	self.totalFatalCnt += other.totalFatalCnt

	for k, v := range other.resT {
		_, ok := self.resT[k]
		if ok {
			self.resT[k] += v
		} else {
			self.resT[k] = v
		}
	}
}

func (self *ActionStat) GetStat() *ActionStatInfo {
	self.statMutex.RLock()
	defer self.statMutex.RUnlock()

	mostResTMs := int32(0)
	mostResTCnt := int32(0)
	for resT, count := range self.resT {
		if count > mostResTCnt {
			mostResTCnt = count
			mostResTMs = resT
		}
	}
	mostResTMs = mostResTMs * 10

	return &ActionStatInfo{
		ActionId:         self.actionId,
		TotalCnt:         self.totalCnt,
		TotalFailSendCnt: self.totalReqCnt - self.totalCnt,
		TotalFatalCnt:    self.totalFatalCnt,
		AvgResTMs:        float32(self.avgTotalResTMs) / float32(self.avgTotalCnt),
		MinResTMs:        self.minResTMs,
		MaxResTMs:        self.maxResTMs,
		MostResTMs:       mostResTMs,
		MostResPercent:   float32(mostResTCnt) / float32(self.totalCnt),
	}
}
